fix(account-pool): preserve provider session affinity - #5
Conversation
There was a problem hiding this comment.
1 issue found across 9 files
Prompt for AI agents (unresolved issues)
Check if these issues are valid — if so, understand the root cause of each and fix them. If appropriate, use sub-agents to investigate and fix each issue separately.
<file name="plugins/account-pool/src/hub.ts">
<violation number="1" location="plugins/account-pool/src/hub.ts:636">
P2: When an older in-flight request fails on the currently-bound account and a newer concurrent request then re-confirms that same account (so the binding still points to it), the older request's retry sees `bound.account.id === previousAccountId` and rebinds the shared session to a different account. This can disrupt the newer request's stable routing even though that account just succeeded for it. The rebind condition `bound.account.id === previousAccountId` does not distinguish 'account I failed on and nobody rebinding since' from 'account a newer request just re-confirmed', and the existing tests only cover the case where the newer binding points to a different account. Consider tracking whether the binding was re-confirmed by a newer request before allowing an older retry to rebind away from it.</violation>
</file>
Reply with feedback, questions, or to request a fix.
Re-trigger cubic
| selected !== null && | ||
| (bound === undefined || | ||
| bound.account.id === selected.account.id || | ||
| bound.account.id === previousAccountId) |
There was a problem hiding this comment.
P2: When an older in-flight request fails on the currently-bound account and a newer concurrent request then re-confirms that same account (so the binding still points to it), the older request's retry sees bound.account.id === previousAccountId and rebinds the shared session to a different account. This can disrupt the newer request's stable routing even though that account just succeeded for it. The rebind condition bound.account.id === previousAccountId does not distinguish 'account I failed on and nobody rebinding since' from 'account a newer request just re-confirmed', and the existing tests only cover the case where the newer binding points to a different account. Consider tracking whether the binding was re-confirmed by a newer request before allowing an older retry to rebind away from it.
Prompt for AI agents
Check if this issue is valid — if so, understand the root cause and fix it. At plugins/account-pool/src/hub.ts, line 636:
<comment>When an older in-flight request fails on the currently-bound account and a newer concurrent request then re-confirms that same account (so the binding still points to it), the older request's retry sees `bound.account.id === previousAccountId` and rebinds the shared session to a different account. This can disrupt the newer request's stable routing even though that account just succeeded for it. The rebind condition `bound.account.id === previousAccountId` does not distinguish 'account I failed on and nobody rebinding since' from 'account a newer request just re-confirmed', and the existing tests only cover the case where the newer binding points to a different account. Consider tracking whether the binding was re-confirmed by a newer request before allowing an older retry to rebind away from it.</comment>
<file context>
@@ -598,7 +618,34 @@ export class AccountPoolHub {
+ selected !== null &&
+ (bound === undefined ||
+ bound.account.id === selected.account.id ||
+ bound.account.id === previousAccountId)
+ ) {
+ this.affinityBindings.delete(affinityKey);
</file context>
Superseded by get-bb#3117 in the correct upstream repository. Closing this misplaced PR.
Human comments
What was wrong
Concurrent requests from one conversation could move between accounts as in-flight load changed. The pool also dropped Codex's native
session-idandthread-idheaders. That disrupted stable request routing and upstream session identity.What changed
Extract session identity once at the provider boundary and keep each provider/host/session on its selected account while that account remains eligible. Rebind when recovery selects another account. Bound the in-memory cache to 4,096 entries with a 30-minute idle expiry and clear it on hub restart.
Read Claude's JSON session metadata and existing legacy session format. Prefer Codex's native
session-id, retain its existingsession_idinput, and useprompt_cache_keyas a separately namespaced fallback. Preserve native headers and request payloads, including encrypted compaction and reasoning items. Requests without a usable identifier retain ordinary account selection.This layer builds on request recovery. Updated the plugin overview and CLI guide. Stable routing supports cache locality. It does not establish actual upstream cache-hit rates or guarantee encrypted-state portability when failover changes accounts.
How you verified
The committed public HTTP/WebSocket regressions failed before implementation: 12 failures covering same-session routing under load, host isolation, idle expiry, account eligibility changes, concurrent first selection, native Codex headers, and LRU eviction.
Tests use synthetic credentials and upstream responses. No live provider cache-hit rate was measured.
Stack
Review and merge in order:
All PRs target
get-bb/bb:main. The source branches remain stacked in the fork. GitHub diffs are currently cumulative because upstream access is read-only, so the parent branches cannot be published there.